package top.guoziyang.mydb.backend.parser;

import top.guoziyang.mydb.common.Error;

/**
 * Tokenizer（词法分析器）,主要功能是从一个字节数组中解析并提取符合一定规则的“标记”（token）
 */


public class Tokenizer {
    private byte[] stat;  // 要解析的字节数组
    private int pos;  // 当前解析的位置
    private String currentToken; // 当前解析的 token
    private boolean flushToken; // 是否需要重新解析新的token
    private Exception err;  // 在解析过程中可能遇到的错误

    public Tokenizer(byte[] stat) {
        this.stat = stat;
        this.pos = 0;
        this.currentToken = "";
        this.flushToken = true;
    }

    /**
     * 获取当前 token
     */
    public String peek() throws Exception {
        if(err != null) {
            throw err;
        }
        if(flushToken) {          // 如果需要解析新的 token
            String token = null;
            try {
                token = next();  // 调用解析方法
            } catch(Exception e) {
                err = e;
                throw e;
            }
            currentToken = token; // 缓存当前解析的 token
            flushToken = false;  // 标记无需重新解析
        }
        return currentToken;
    }

    /**
     * 将状态标记为需要重新解析 token,表示下次调用 peek 时需要重新解析 token
     */
    public void pop() {
        flushToken = true;
    }

    /**
     * 生成错误状态，当解析出错时，显示错误状态，明确标示解析失败的位置。
     * 例如:stat = {'a', 'b', 'c', 'd', 'e'}（对应字符串 "abcde"）,pos = 2（表示解析错误发生在位置 2）
     * 结果为: res = {'a', 'b', '<', '<', ' ', 'c', 'd', 'e'},对应字符串为 "ab<< cde"
     */
    public byte[] errStat() {
        byte[] res = new byte[stat.length+3];
        System.arraycopy(stat, 0, res, 0, pos);
        System.arraycopy("<< ".getBytes(), 0, res, pos, 3);
        System.arraycopy(stat, pos, res, pos+3, stat.length-pos);
        return res;
    }

    /**
     * 消耗当前字节，将解析位置向后移动
     */
    private void popByte() {
        pos ++;
        if(pos > stat.length) {
            pos = stat.length;
        }
    }

    /**
     * 返回当前字节，但不改变解析位置
     */
    private Byte peekByte() {
        if(pos == stat.length) {
            return null;
        }
        return stat[pos];
    }

    /**
     * 解析下一个 token，核心逻辑通过 nextMetaState 方法实现
     */
    private String next() throws Exception {
        if(err != null) {
            throw err;
        }
        return nextMetaState();
    }

    /**
     * 解析不同类型的 token:
     * 跳过空白字符。
     * 如果是符号（如 >, <, =, *, ,, (, ) 等），直接返回。
     * 如果是引号包裹的内容（如 "abc"），调用 nextQuoteState 解析。
     * 如果是标识符（如变量名或数字），调用 nextTokenState 解析。
     */
    private String nextMetaState() throws Exception {
        // 循环读取字节，定位到非空白字符
        while(true) {
            Byte b = peekByte(); // 查看当前字节
            if(b == null) {   // 如果到达字节数组末尾，返回空字符串
                return "";
            }
            if(!isBlank(b)) {  // 如果当前字节不是空白字符，结束循环
                break;
            }
            popByte();   // 如果是空白字符，消耗掉，继续查看下一个字节
        }
        byte b = peekByte(); // 获取当前非空白字符
        if(isSymbol(b)) {   // 如果是符号，直接返回
            popByte();  // 消耗该符号
            return new String(new byte[]{b}); // 返回符号作为一个 token
        } else if(b == '"' || b == '\'') {  // 如果是引号，进入引号解析状态
            return nextQuoteState();
        } else if(isAlphaBeta(b) || isDigit(b)) { // 如果是字母或数字，进入标识符解析状态
            return nextTokenState();
        } else {              // 如果是非法字符，抛出异常
            err = Error.InvalidCommandException;
            throw err;
        }
    }

    /**
     * 解析由字母、数字或下划线组成的标识符 token
     */
    private String nextTokenState() throws Exception {
        StringBuilder sb = new StringBuilder();
        while(true) {
            Byte b = peekByte(); // 查看当前字节
            if(b == null || !(isAlphaBeta(b) || isDigit(b) || b == '_')) {  // 如果不是字母、数字或下划线，停止解析
                if(b != null && isBlank(b)) {
                    popByte();   // 跳过空白字符
                }
                return sb.toString();  // 返回已解析的 token
            }
            sb.append(new String(new byte[]{b}));   // 追加当前字符到 token
            popByte(); // 消耗当前字符
        }
    }

    /**
     * 判断字节是否为数字
     */
    static boolean isDigit(byte b) {
        return (b >= '0' && b <= '9');
    }

    /**
     * 判断字节是否为字母
     */
    static boolean isAlphaBeta(byte b) {
        return ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'));
    }

    /**
     * 解析引号包裹的内容（如 "hello"），直到遇到配对引号为止
     */
    private String nextQuoteState() throws Exception {
        byte quote = peekByte();  // 获取引号类型（`"` 或 `'`）
        popByte();  // 消耗引号
        StringBuilder sb = new StringBuilder();
        while(true) {
            Byte b = peekByte();  // 查看当前字符
            if(b == null) {    // 如果到达末尾，抛出异常
                err = Error.InvalidCommandException;
                throw err;
            }
            if(b == quote) {  // 如果遇到配对引号，结束解析
                popByte();
                break;
            }
            sb.append(new String(new byte[]{b})); // 追加字符到 token
            popByte();  // 消耗当前字符
        }
        return sb.toString(); // 返回引号包裹的内容
    }

    /**
     * 判断字节是否为符号
     */
    static boolean isSymbol(byte b) {
        return (b == '>' || b == '<' || b == '=' || b == '*' ||
		b == ',' || b == '(' || b == ')');
    }

    /**
     * 判断字节是否为空白
     */
    static boolean isBlank(byte b) {
        return (b == '\n' || b == ' ' || b == '\t');
    }
}
